/** storage 通用选项 */
interface StorageOptions {
  /** 是否同步获取 */
  sync: boolean
  /** 有效期, 单位分钟 */
  expires: number
  /** 前缀 */
  prefix: string
  /** 获取时是否包含已过期 */
  includeExpired: boolean
}

/** 存储在storage中的内容 */
interface StorageValue<T = any> {
  data: T
  /** 过期时间 */
  expiredAt: Date
}

/** storage 存储类 */
export class Storage {
  private options: StorageOptions = {
    sync: true,
    expires: Infinity,
    prefix: '',
    includeExpired: false,
  }

  public constructor(opts: Partial<StorageOptions> = {}) {
    this.options = Object.assign({}, this.options, opts)
  }

  /**
   * 从 wx.Storage 获取值
   *
   * 如果值已经过期, 则返回空字符串
   *
   * @param options.sync 是否同步获取
   * @param options.includeExpired 如果为 `true` 则, 过期也可获取内容
   */
  public get<T = any>(key: string, options?: Partial<StorageOptions>): T | ''
  public async get<T = any>(key: string, options?: Partial<StorageOptions>): Promise<T | ''>
  public get<T = any>(
    key: string,
    options: Partial<StorageOptions> = {},
  ): T | '' | Promise<T | ''> {
    const opts: StorageOptions = Object.assign({}, this.options, options)
    if (opts.prefix) key = `${opts.prefix}${key}`
    let value: StorageValue<T>
    if (opts.sync) {
      // 同步
      value = wx.getStorageSync(key)
      return +value.expiredAt < Date.now() ? '' : value.data
    } else {
      // 异步
      return new Promise((resolve, reject) => {
        wx.getStorage({
          key,
          success: (res) => {
            value = res.data
            if (+value.expiredAt < Date.now()) return resolve('')
            else return resolve(value.data)
          },
          fail: (err) => reject(err),
        })
      })
    }
  }

  /**
   * 向 wx.Storage 存储值
   *
   * @param options.sync 是否同步存储
   * @param options.expires 有效期, 单位分钟, 默认永不过期
   */
  public set<T = any>(
    key: string,
    data: T,
    options: Partial<StorageOptions> = {},
  ): void | Promise<unknown> {
    const opts: StorageOptions = Object.assign({}, this.options, options)
    if (opts.prefix) key = `${opts.prefix}${key}`
    const value: StorageValue<T> = {
      data,
      expiredAt: new Date(Date.now() + opts.expires * 60000),
    }
    if (opts.sync) return void wx.setStorageSync(key, value)
    return new Promise((resolve, reject) => {
      wx.setStorage({
        key,
        data: value,
        success: (res) => resolve(res),
        fail: (err) => reject(err),
      })
    })
  }

  /**
   * 判断某个值是否过期, 如果不存在则返回 null
   */
  public isExpired(key: string): null | boolean {
    const value = this.get(key, { includeExpired: true, sync: true })
    if (!value) return null
    return !this.get(key, { includeExpired: false, sync: true })
  }

  /** 从 storage 移除某值 */
  public remove(key: string, options: Partial<StorageOptions> = {}) {
    const opts: StorageOptions = Object.assign({}, this.options, options)
    if (opts.prefix) key = `${opts.prefix}${key}`
    wx.removeStorageSync(key)
  }

  /**
   * 清空 storage
   *
   * @param options.sync 是否同步清除
   */
  public clear(options: Partial<StorageOptions> = {}) {
    const opts: StorageOptions = Object.assign({}, this.options, options)
    if (opts.sync) wx.clearStorageSync()
    else wx.clearStorage()
  }
}
